/* SPDX-License-Identifier: MIT */

package com.keyidea.jfxui;

import com.goxr3plus.fxborderlessscene.borderless.BorderlessPane;
import com.goxr3plus.fxborderlessscene.borderless.BorderlessScene;
import com.keyidea.jfxui.core.util.MemStorage;
import com.keyidea.jfxui.core.util.YamlUtil;
import com.keyidea.jfxui.portal.config.DefaultExceptionHandler;
import com.keyidea.jfxui.portal.config.Resources;
import com.keyidea.jfxui.portal.config.event.BrowseEvent;
import com.keyidea.jfxui.portal.config.event.DefaultEventBus;
import com.keyidea.jfxui.portal.config.event.HotkeyEvent;
import com.keyidea.jfxui.portal.config.event.Listener;
import com.keyidea.jfxui.portal.config.theme.ThemeManager;
import com.keyidea.jfxui.portal.view.window.WindowView;
import com.keyidea.jfxui.portal.view.window.WindowViewModel;
import de.saxsys.mvvmfx.FluentViewLoader;
import de.saxsys.mvvmfx.ViewTuple;
import fr.brouillard.oss.cssfx.CSSFX;
import fr.brouillard.oss.cssfx.api.URIToPathConverter;
import fr.brouillard.oss.cssfx.impl.log.CSSFXLogger;
import fr.brouillard.oss.cssfx.impl.log.CSSFXLogger.LogLevel;
import javafx.application.Application;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.SneakyThrows;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

import java.net.URI;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;

public class AppUI extends Application {

    private ConfigurableApplicationContext configurableApplicationContext;

    public static final String CSS_FILE_PATH = "portal/assets/styles/index.css";

    public static final String DEV_MODE_MARK = "DEV";

    public static final boolean IS_DEV_MODE = DEV_MODE_MARK.equalsIgnoreCase(
            Resources.getPropertyOrEnv("Datlantafx.mode", "DATLANTAFX_MODE")
    );

    public static final List<KeyCodeCombination> SUPPORTED_HOTKEYS = List.of(
            new KeyCodeCombination(KeyCode.SLASH),
            new KeyCodeCombination(KeyCode.T, KeyCombination.CONTROL_DOWN),
            new KeyCodeCombination(KeyCode.W, KeyCombination.CONTROL_DOWN)
    );

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        Thread.currentThread().setUncaughtExceptionHandler(new DefaultExceptionHandler(stage));
        loadApplicationProperties();

        if (IS_DEV_MODE) {
            System.out.println("[WARNING] Application is running in development mode.");
        }

        ViewTuple<WindowView, WindowViewModel> viewTuple = FluentViewLoader.fxmlView(WindowView.class).load();
        Parent mainView = viewTuple.getView();
        BorderlessScene scene = new BorderlessScene(stage, StageStyle.TRANSPARENT, mainView, 1200 + 80, 768);
        BorderlessPane rootPane = (BorderlessPane) scene.getRoot();
        rootPane.setPadding(new Insets(15, 15, 15, 15));

        scene.setOnKeyPressed(this::dispatchHotkeys);
        scene.removeDefaultCSS();
        scene.setMoveControl(mainView);
        scene.setFill(Color.TRANSPARENT);

        var tm = ThemeManager.getInstance();
        tm.setScene(scene);
        tm.setTheme(tm.getDefaultTheme());
        if (IS_DEV_MODE) {
            startCssFX(scene);
        }
        scene.maximizedProperty().addListener((observableValue, oldVal, newVal) -> {
            if (newVal) {
                rootPane.setPadding(new Insets(0, 0, 0, 0));
            } else {
                rootPane.setPadding(new Insets(15, 15, 15, 15));
            }
        });
        scene.getStylesheets().addAll(Resources.resolve(CSS_FILE_PATH));

        stage.setScene(scene);
        stage.setTitle(YamlUtil.getValueStr(MemStorage.CONFIG_PROPERTIES, "app", "name"));

        loadIcons(stage);
        stage.setResizable(true);
        stage.setOnCloseRequest(t ->
                {
                    Platform.exit();
                    configurableApplicationContext.close();
                    System.exit(0);
                }
        );

        // register event listeners
        DefaultEventBus.getInstance().subscribe(BrowseEvent.class, this::onBrowseEvent);

        Platform.runLater(() -> {
            stage.show();
            stage.requestFocus();
        });
    }


    private void loadIcons(Stage stage) {
        // 设置icon
        Image image = new Image(Objects.requireNonNull(getClass().getResource("/images/icon.png")).toExternalForm());
        stage.getIcons().addAll(image);
    }

    @SneakyThrows
    private void loadApplicationProperties() {
        MemStorage.CONFIG_PROPERTIES = YamlUtil.ConfigReader.getInstance();
    }

    @SuppressWarnings("CatchAndPrintStackTrace")
    private void startCssFX(Scene scene) {
        URIToPathConverter fileUrlConverter = uri -> {
            try {
                if (uri != null && uri.startsWith("file:")) {
                    return Paths.get(URI.create(uri));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        };

        CSSFX.addConverter(fileUrlConverter).start();
        CSSFXLogger.setLoggerFactory(loggerName -> (level, message, args) -> {
            if (level.ordinal() <= LogLevel.INFO.ordinal()) {
                System.out.println("[" + level + "] CSSFX: " + String.format(message, args));
            }
        });
        CSSFX.start(scene);
    }

    private void dispatchHotkeys(KeyEvent event) {
        for (KeyCodeCombination k : SUPPORTED_HOTKEYS) {
            if (k.match(event)) {
                DefaultEventBus.getInstance().publish(new HotkeyEvent(k));
                return;
            }
        }
    }

    @Override
    public void init() throws Exception {
        super.init();
        configurableApplicationContext = new SpringApplicationBuilder(AppStart.class).run();
    }

    @Listener
    private void onBrowseEvent(BrowseEvent event) {
        getHostServices().showDocument(event.getUri().toString());
    }
}
